/* 
 * File:   main.cpp
 * Author: mauro
 *
 * Created on 28 de septiembre de 2014, 00:07
 */

#include <cstdlib>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>

#include <thread>
#include <vector>
#include <queue>

#include "Model/MundoSBS.h"
#include "Model/Player.h"
#include "Define/DefaultValues.h"


using namespace std;

#define ERROR -1
SBSLog* myLogFile;

int main(int argc, char** argv) {

    /*********************************************************************
     *VARIABLES DEL SERVIDOR                                             *
     *********************************************************************/
    myLogFile->getInstance(MY_LOG_MODE).setMyTimeStamp(); //graba el timestamp de la corrida 

    //    char address[] = "192.168.0.103";
    //      char address[] = "127.0.0.1";
    char address[] = "10.42.0.66";
    int port = 8080;

    myLogFile->writeInLog(0, "[0] IP del server: ");
    myLogFile->writeInLog(0, address);
    myLogFile->writeInLog(0, "/n");

    myLogFile->writeInLog(0, "[0] Puerto del server: ");
    myLogFile->writeInLog(0, to_string(port));
    myLogFile->writeInLog(0, "/n");

    cout << "SERVER" << endl;
    cout << "IP: " << address << "  PORT: " << port << endl;

    /*********************************************************************
     * INICIAR JUEGO                                                     *
     *********************************************************************/
    bool end = false;

    MundoSBS mundoSBS;

    mundoSBS.initData(argc, argv); //archivo de JSON
    mundoSBS.initData2(argc, argv); //archivo de JSON

    mundoSBS.createWorld(); // creo el mundo box2d
    mundoSBS.initGame(); // creo los bodies para el mundo

    mundoSBS.initServer(address, port); //accept, thread

    //crea un hilo de conexion.
    mundoSBS.getConection();

    // solo sale cuando se llega a la cantidad de jugadores
    while (!mundoSBS.playersAreReady()) {
        // si el hilo de conection detecto nuevo jugador
        if (mundoSBS.arePlayersWaiting()) {
            // envio los jugadores nuevos a los jugadores que tenia
            mundoSBS.sendPlayer();
            mundoSBS.actualizePlayerList();
        }
    }

    //    cout << "Cantidad de players conectados " << mundoSBS.getPlayers()->size() << endl;
    mundoSBS.runThreads();

    // tambien podria verse como cambiar para no setear los bodys en el world antes de lanzar los hilos
    list <Player*> *listPlayers = mundoSBS.getPlayers();
    for (list <Player*>::iterator it = listPlayers->begin(); it != listPlayers->end(); ++it) {
        Player *itPlayer = *it;
        mundoSBS.sendInitialWorld(itPlayer);
    }


    /*********************************************************************
     * STEP BOX2D                                               
     *********************************************************************/

    //NOTA: en este caso, cuanto mas fps, el server envia mas elemento al cliente.
    float fps = 0.01; // 0.02 = 1/50 = 50fps 
    //    float fps = 0.04; // server Mau = 30fps    
    timespec iniTime, endTime;
    float sleep;

    //    float32 timeStep = 1.0f / 100.0f;
    float32 timeStep = fps;
    int32 velocityIterations = 1;
    int32 positionIterations = 10;
    usleep(50000); //espera

    bool reloadLevel1 = false;
    while (!end) {

        mundoSBS.erasePlayersNotInit();

        //USUARIOS
        list <Player*> *players = mundoSBS.getPlayers();

        if (!mundoSBS.playersReadyToReload()) {//los jugadores estan todos para arrancar
            // lanzo pantalla de waiting (el cliente la levanta con level -1)
            for (list <Player*>::iterator it = players->begin(); it != players->end(); ++it) {
                Player *player = *it;
                struct ObjectInit obj;
                obj.idCharacter = VACIO;
                obj.level = -1; // PANTALLA WAITING

                player->sendLevelFinished(obj);
            }
            // mientras que no esten los suficientes jugadores init
            while (!mundoSBS.playersReadyToReload()) {

                //cout << "waiting playersssss" << endl;

                usleep(1000000);

                if (mundoSBS.areFuturesPlayers()) {
                    struct ContenedorSocket data = mundoSBS.getFirstFuturePlayer();
                    mundoSBS.createPlayer(data.socket, data.player);
                }

                if (mundoSBS.arePlayersWaiting()) {
                    mundoSBS.actualizePlayerList();
                }
            }

            // aviso que se reinicia nivel (cliente lo toma como un flag para salir de la pantalla waiting)
            for (list <Player*>::iterator it = players->begin(); it != players->end(); ++it) {
                Player *player = *it;
                struct ObjectInit obj;
                obj.idCharacter = VACIO;
                obj.level = -1;

                player->sendLevelFinished(obj);
            }
        }

        if (reloadLevel1) {

            struct ObjectInit myObj;
            myObj.idCharacter = VACIO;
            myObj.level = 1;
            for (list <Player*>::iterator it = players->begin(); it != players->end(); ++it) {
                Player *player = *it;
                player->sendLevelFinished(myObj); // Flag para indicar inicio del nuevo nivel
            }

            mundoSBS.clean(); //limpia todos los vectores menos el de auxPlayers y carga este ultimo con los personajes del nivel 2
            mundoSBS.resetPlayers(); //vuelve a setear los characters y los carga en el vector de dynamicos
            mundoSBS.initGame(); //Crear mundo Box2D (se cargan todos los objetos menos los personajes) Y SE HACE DELETE DE LOS CHARACTERS que estan en auxPlayers (nivel 2)

            for (list <Player*>::iterator it = players->begin(); it != players->end(); ++it) {
                Player *player = *it;
                mundoSBS.sendInitialWorld(player);
            }
        }

        //cout << "INI LEVEL 1" << endl;

        reloadLevel1 = true;
        mundoSBS.setReloadLevel(true);
        //int iniMau = 0;
        while (mundoSBS.areEnemiesAlive()) {
//            if (iniMau == 0) {
//                cout << "ini Mau" << endl;
//                for (list <Bullet*>::iterator it = mundoSBS.getBulletList().begin(); it != mundoSBS.getBulletList().end(); ++it) {
//                    Bullet *bullet = *it;
//                    bullet->setStat(1);
//                    iniMau++;
//                }
//            }
            if (mundoSBS.gameOver()) {
                struct ObjectInit myObj;
                myObj.idCharacter = VACIO;
                myObj.objectId = VACIO;

                bool playerNotInit = false; // todos los players vivos
                for (list <Player*>::iterator it = players->begin(); it != players->end(); ++it) {
                    Player *player = *it;

                    if (!(player->isInit())) {
                        playerNotInit = true;
                    }
                }

                if (playerNotInit) {
                    // el cliente tiene que setear gameOver e ir a la waiting
                    //cout << "MANDO AL CLIENTE A WAITING Y GAME OVERRRRRRRRRRRRRRRRRRRR"<<endl;
                    myObj.level = 4;
                } else {
                    myObj.level = 3;
                }

                for (list <Player*>::iterator it = players->begin(); it != players->end(); ++it) {
                    Player *player = *it;
                    player->sendLevelFinished(myObj); // Flag para indicar GAME OVER
                }
                break;
            }

            //BOX2D
            mundoSBS.getWorld()->Step(timeStep, velocityIterations, positionIterations);
            mundoSBS.getWorld()->ClearForces();

            //muevo a los malos
            mundoSBS.makeEnemiesMove();

            mundoSBS.deleteOldBonus();

            // Chequeo que elem dyn se modificaron
            mundoSBS.checkChanges();

            usleep(sleep); // espera por los fps

            if (players->size() > 0) { //tengo jugadores en linea.
                clock_gettime(CLOCK_REALTIME, &iniTime);
                for (list <Player*>::iterator it = players->begin(); it != players->end(); ++it) {
                    bool recivedEvent = false;
                    Player *player = *it;
                    if (player->isInit()) {
                        /*********************************************************************
                         * EVENTOS DEL JUGADOR                                               
                         *********************************************************************/
                        struct Event e;
                        if (!player->tryPop(e)) {
                            e.pressed = '@';
                            e.released = NULL;
                        } else {
                            recivedEvent = true;
                        }

                        switch (player->getState()) {
                            case JUMPING://jumping
                                if (e.released != NULL) {
                                    switch (e.released) {
                                        case 'L':
                                            player->stopWalking();
                                            break;
                                        case 'R':
                                            player->stopWalking();
                                            break;
                                    }
                                } else {
                                    switch (e.pressed) {
                                        case 'L':
                                            player->jumpingLeft();
                                            break;
                                        case 'R':
                                            player->jumpingRight();
                                            break;
                                        case 'B':
                                            if (player->isInContactSnowBallEnemy()) {
                                                e.released = 'P';
                                            } else {
                                                e.released = 'S';
                                                mundoSBS.setBullets(player->shoot());
                                            }
                                            break;
                                    }
                                }
                                player->keepJumping();
                                break;
                            case KNEELING: //kneeling
                                if (e.released != NULL) {
                                    switch (e.released) {
                                        case 'D':
                                            player->stopKneeling();
                                            break;
                                    }
                                } else {
                                    switch (e.pressed) {
                                        case 'B':
                                            if (player->isInContactSnowBallEnemy()) {
                                                e.released = 'P';
                                            } else {
                                                e.released = 'S';
                                                mundoSBS.setBullets(player->shoot());
                                            }
                                            break;
                                    }
                                }
                                player->keepKneeling();
                                break;
                            case FALLING: //fallingDown
                                if (e.pressed != NULL || e.released != NULL) {
                                    if (e.pressed != NULL) {
                                        switch (e.pressed) {
                                            case 'L':
                                                player->fallLeft();
                                                break;
                                            case 'R':
                                                player->fallRight();
                                                break;
                                            case 'B':
                                                if (player->isInContactSnowBallEnemy()) {
                                                    e.released = 'P';
                                                } else {
                                                    e.released = 'S';
                                                    mundoSBS.setBullets(player->shoot());
                                                }
                                                break;
                                        }
                                    }
                                    if (e.released != NULL) {
                                        switch (e.released) {
                                            case 'L':
                                                player->stopWalking();
                                                break;
                                            case 'R':
                                                player->stopWalking();
                                                break;
                                        }
                                    }
                                }
                                player->keepFallingDown();
                                break;
                            case WALKING: //walking
                                if (e.pressed != NULL || e.released != NULL) {
                                    if (e.pressed != NULL) {
                                        switch (e.pressed) {
                                            case 'U':
                                                player->jump();
                                                break;
                                            case 'B':
                                                if (player->isInContactSnowBallEnemy()) {
                                                    e.released = 'P';
                                                } else {
                                                    e.released = 'S';
                                                    mundoSBS.setBullets(player->shoot());
                                                }
                                                break;
                                        }
                                    }
                                    if (e.released != NULL) {
                                        switch (e.released) {
                                            case 'L':
                                                if (!player->isWalkingRight()) {
                                                    player->stopWalking();
                                                }
                                                break;
                                            case 'R':
                                                if (!player->isWalkingLeft()) {
                                                    player->stopWalking();
                                                }
                                                break;
                                        }
                                    }
                                }
                                player->keepWalking();
                                break;
                            case WAITING: // checkEvents (Waiting)
                                if (e.pressed != NULL || e.released != NULL) {
                                    if (e.pressed != NULL) {
                                        switch (e.pressed) {
                                            case 'L':
                                                player->walkLeft();
                                                break;
                                            case 'R':
                                                player->walkRight();
                                                break;
                                            case 'U':
                                                player->jump();
                                                break;
                                            case 'D':
                                                player->kneel();
                                                break;
                                            case 'B':
                                                if (player->isInContactSnowBallEnemy()) {
                                                    e.pressed = 'P';
                                                } else {
                                                    e.pressed = 'S';
                                                    mundoSBS.setBullets(player->shoot());
                                                }
                                                break;
                                            case '@':
                                                player->wait();
                                                break;
                                        }
                                    } else {
                                        player->wait();
                                    }
                                } else {
                                    player->wait();
                                }
                        }


                        if (player->isInContactBonus()) {
                            mundoSBS.deleteBonus();
                            e.released != NULL;
                            e.pressed = 'C';
                        }
                        if (player->isInContactEnemy()) {
                            if (player->isWatching()) {
                                //                                cout << "Tenemos un observadoooooooooooooooooooooooooooooooor" << endl;
                            }
                        }

                        // esto era para que lo arrastre la pelota
                        // if (player->isInContactCrazyBall()) {
                        //      player->setTrapped();
                        // }

                        // updeteo el evento en el data del player
                        player->updateEvent(e);

                        if (recivedEvent) {
                            // pusheo los obj dyn del mundo al player
                            mundoSBS.setUpdatedData(player);
                        }

                    } else {//isInit    // se desconecto
                        mundoSBS.killBodyPlayer(player);
                    }//por cada jugador, tomo el primer evento.                
                }//for

                clock_gettime(CLOCK_REALTIME, &endTime);
                float deltaTime = endTime.tv_nsec - iniTime.tv_nsec;

                sleep = fps - (deltaTime * pow(10, -9)); // paso a seg            
                sleep = sleep * pow(10, 6); // convierto a unidades de usleep

                if (sleep < 0) {
                    sleep = 500; // sleep default por las dudas..
                }

            }//players->size

        }//fin LEVEL 1

        reloadLevel1 = true;
        mundoSBS.setReloadLevel(true);

        if (!mundoSBS.gameOver()) {


            /******************************************************************
             * INICIO DEL NIVEL 2
             *******************************************************************/
            //#NOTA: hace una limpieza de las colas tanto de send y recv.

            //        if (mundoSBS.isGameOver()) {
            //            cout << "game over" << endl;
            //            struct ObjectInit myObj;
            //            myObj.level = 10;
            //            for (list <Player*>::iterator it = players->begin(); it != players->end(); ++it) {
            //                Player *player = *it;
            //                player->sendLevelFinished(myObj); // Flag para indicar inicio del nuevo nivel
            //            }// 
            //            break;
            //            // continue;
            //        }
            //        else {
            //cout << "INI LEVEL 2" << endl;
            struct ObjectInit myObj;
            myObj.idCharacter = VACIO;
            myObj.level = 2;
            for (list <Player*>::iterator it = players->begin(); it != players->end(); ++it) {
                Player *player = *it;
                player->sendLevelFinished(myObj); // Flag para indicar inicio del nuevo nivel
            }
            //cout << "iniii clean" << endl;
            mundoSBS.clean(); //se borran todos los vectores menos el de players y se carga este ultimo con los characters del nivel 1
            //cout << "fin clean" << endl;
            mundoSBS.initGame2(); //Crear mundo Box2D (se cargan todos los objetos + los characteres del nivel 1)
            //cout << "ini game 2" << endl;

            //Primer tanda de datos al cliente.
            for (list <Player*>::iterator it = players->begin(); it != players->end(); ++it) {
                Player *player = *it;
                mundoSBS.sendInitialWorld(player); //envia a cada player los dynamic bodies 
                mundoSBS.sendInitialWorldStatics(player); //envia a cada player los static bodies
                player->changeToPosInitial();
            }

            bool primeraVez = true;
            //int iniLau=0;
            while (mundoSBS.isBossAlive()) {
//                if (iniLau == 0) {
//                    cout << "ini Lau" << endl;
//                    for (list <Bullet*>::iterator it = mundoSBS.getBulletList().begin(); it != mundoSBS.getBulletList().end(); ++it) {
//                        Bullet *bullet = *it;
//                        bullet->setStat(1);
//                        iniLau++;
//                    }
//                }
                if (mundoSBS.gameOver()) {
                    struct ObjectInit myObj;
                    myObj.idCharacter = VACIO;
                    myObj.objectId = VACIO;

                    bool playerNotInit = false; // todos los players vivos
                    for (list <Player*>::iterator it = players->begin(); it != players->end(); ++it) {
                        Player *player = *it;

                        if (!(player->isInit())) {
                            playerNotInit = true;
                        }
                    }

                    if (playerNotInit) {
                        // el cliente tiene que setear gameOver e ir a la waiting

                        cout << "MANDO AL CLIENTE A WAITING Y GAME OVERRRRRRRRRRRRRRRRRRRR" << endl;
                        myObj.level = 4;
                    } else {
                        myObj.level = 3;
                    }

                    for (list <Player*>::iterator it = players->begin(); it != players->end(); ++it) {
                        Player *player = *it;
                        player->sendLevelFinished(myObj); // Flag para indicar GAME OVER
                    }
                    break;
                }

                //BOX2D
                mundoSBS.getWorld()->Step(timeStep, velocityIterations, positionIterations);
                mundoSBS.getWorld()->ClearForces();
                //muevo a los malos
                mundoSBS.makeEnemiesMove();
                mundoSBS.makeBossMove();

                mundoSBS.deleteOldBonus();
                // Chequeo que elem dyn se modificaron
                mundoSBS.checkChanges();
                usleep(sleep); //espera
                list <Player*> *players = mundoSBS.getPlayers();
                if (players->size() > 0) { //tengo jugadores en linea.

                    clock_gettime(CLOCK_REALTIME, &iniTime);
                    for (list <Player*>::iterator it = players->begin(); it != players->end(); ++it) {
                        bool recivedEvent = false;
                        Player *player = *it;


                        if (player->isInit()) {

                            if (primeraVez) {
                                player->stopWalking();

                                primeraVez = false;
                            }
                            /*********************************************************************
                             * EVENTOS DEL JUGADOR                                               
                             *********************************************************************/
                            struct Event e;
                            if (!player->tryPop(e)) {
                                e.pressed = '@';
                                e.released = NULL;
                            } else {
                                recivedEvent = true;
                            }


                            switch (player->getState()) {
                                case JUMPING://jumping
                                    if (e.released != NULL) {
                                        switch (e.released) {
                                            case 'L':
                                                player->stopWalking();
                                                break;
                                            case 'R':
                                                player->stopWalking();
                                                break;
                                        }
                                    } else {
                                        switch (e.pressed) {
                                            case 'L':
                                                player->jumpingRight();
                                                break;
                                            case 'R':
                                                player->jumpingLeft();
                                                break;
                                            case 'B':
                                                if (player->isInContactSnowBallEnemy()) {
                                                    e.released = 'P';
                                                } else {
                                                    e.released = 'S';
                                                    mundoSBS.setBullets(player->shoot());
                                                }
                                                break;
                                        }
                                    }
                                    player->keepJumping();
                                    break;
                                case KNEELING: //kneeling
                                    if (e.released != NULL) {
                                        switch (e.released) {
                                            case 'D':
                                                player->stopKneeling();
                                                break;
                                        }
                                    } else {
                                        switch (e.pressed) {
                                            case 'B':
                                                if (player->isInContactSnowBallEnemy()) {
                                                    e.released = 'P';
                                                } else {
                                                    e.released = 'S';
                                                    mundoSBS.setBullets(player->shoot());
                                                }
                                                break;
                                        }
                                    }
                                    player->keepKneeling();
                                    break;
                                case FALLING: //fallingDown
                                    if (e.pressed != NULL || e.released != NULL) {
                                        if (e.pressed != NULL) {
                                            switch (e.pressed) {
                                                case 'L':
                                                    player->fallLeft();
                                                    break;
                                                case 'R':
                                                    player->fallRight();
                                                    break;
                                                case 'B':
                                                    if (player->isInContactSnowBallEnemy()) {
                                                        e.released = 'P';
                                                    } else {
                                                        e.released = 'S';
                                                        mundoSBS.setBullets(player->shoot());
                                                    }
                                                    break;
                                            }
                                        }
                                        if (e.released != NULL) {
                                            switch (e.released) {
                                                case 'L':
                                                    player->stopWalking();
                                                    break;
                                                case 'R':
                                                    player->stopWalking();
                                                    break;
                                            }
                                        }
                                    }
                                    player->keepFallingDown();
                                    break;
                                case WALKING: //walking
                                    if (e.pressed != NULL || e.released != NULL) {
                                        if (e.pressed != NULL) {
                                            switch (e.pressed) {
                                                case 'U':
                                                    player->jump();
                                                    break;
                                                case 'B':
                                                    if (player->isInContactSnowBallEnemy()) {
                                                        e.released = 'P';
                                                    } else {
                                                        e.released = 'S';
                                                        mundoSBS.setBullets(player->shoot());
                                                    }
                                                    break;
                                            }
                                        }
                                        if (e.released != NULL) {
                                            switch (e.released) {
                                                case 'L':
                                                    if (!player->isWalkingRight()) {
                                                        player->stopWalking();
                                                    }
                                                    break;
                                                case 'R':
                                                    if (!player->isWalkingLeft()) {
                                                        player->stopWalking();
                                                    }
                                                    break;
                                            }
                                        }
                                    }
                                    player->keepWalking();
                                    break;
                                case WAITING: // checkEvents (Waiting)
                                    if (e.pressed != NULL || e.released != NULL) {
                                        if (e.pressed != NULL) {
                                            switch (e.pressed) {
                                                case 'L':
                                                    player->walkLeft();
                                                    break;
                                                case 'R':
                                                    player->walkRight();
                                                    break;
                                                case 'U':
                                                    player->jump();
                                                    break;
                                                case 'D':
                                                    player->kneel();
                                                    break;
                                                case 'B':
                                                    if (player->isInContactSnowBallEnemy()) {
                                                        e.pressed = 'P';
                                                    } else {
                                                        e.pressed = 'S';
                                                        mundoSBS.setBullets(player->shoot());
                                                    }
                                                    break;
                                                case '@':
                                                    player->wait();
                                                    break;
                                            }
                                        } else {
                                            player->wait();
                                        }
                                    } else {
                                        player->wait();
                                    }
                            }

                            if (player->isInContactBonus()) {
                                mundoSBS.deleteBonus();
                                e.released != NULL;
                                e.pressed = 'C';
                            }
                            if (player->isInContactEnemy()) {

                            }


                            // updeteo el evento en el data del player
                            player->updateEvent(e);

                            if (recivedEvent) {
                                // pusheo los obj dyn del mundo al player
                                mundoSBS.setUpdatedData(player);
                            }

                        } else {//isInit    // se desconecto
                            mundoSBS.killBodyPlayer(player);

                        }//por cada jugador, tomo el primer evento.                
                    }//for

                    clock_gettime(CLOCK_REALTIME, &endTime);
                    float deltaTime = endTime.tv_nsec - iniTime.tv_nsec;

                    sleep = fps - (deltaTime * pow(10, -9)); // paso a seg            
                    sleep = sleep * pow(10, 6); // convierto a unidades de usleep

                    if (sleep < 0) {
                        sleep = 500; // sleep default por las dudas..
                    }

                }//players->size
            }
        }
        //cout << "Reload level" << endl;

    }//while

    myLogFile->writeInLog(0, "[0] Ha finalizado el juego, gracias por participar =) \n");


    return 0;
}




